﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;

using System.Threading;

using DatabaseLayer;
using YouTubeAccessLayer;
using FileFolderOperations;

namespace IVRJunctionSynchronizationService
{
    public partial class IVRJunctionSynchronization : ServiceBase
    {
        private Thread threadServiceLogic;
        private DatabaseFunctions objectDatabaseFunctions;
        private FileFunctions objectFileFunctions;

        // configuration data
        private String developerKey;
        private String accountName;
        private String username;
        private String password;
        private String branchId;
        private String dropBoxFolderLocation;

        //private bool isModerationOnline;
        private bool isSynchronizeBranch;

        private String sSource = "IVR Junction- Synchronization Service";
        private String sLog = "Application";

        public IVRJunctionSynchronization()
        {
            InitializeComponent();
            objectDatabaseFunctions = new DatabaseFunctions();
            objectFileFunctions = new FileFunctions();
            this.ServiceName = "IVR Junction- Synchronization Service";

            // setting up the event logger
            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);
            EventLog.WriteEntry(sSource, "Constructor Started");

            DatabaseFunctions objectDB = new DatabaseFunctions();
            var configurationData = objectDB.getIVRJunctionConfiguration();
            // if the configuration data is set and readable then execute the service code
            if (configurationData != null && configurationData.Item1 != null)
            {
                branchId = configurationData.Item1;

                var youTubeConfigurationInformation = configurationData.Item3;
                developerKey = youTubeConfigurationInformation.Item1;
                accountName = youTubeConfigurationInformation.Item2;
                username = youTubeConfigurationInformation.Item3;
                password = youTubeConfigurationInformation.Item4;

                // appending '\\'  as a file name would be added to this folder later
                dropBoxFolderLocation = configurationData.Item5+"\\";
                 
                var genericConfigurationInformationTuple = configurationData.Item6;
                isSynchronizeBranch = genericConfigurationInformationTuple.Item2;
            }
            else
                this.Stop();
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);
            try
            {
                threadServiceLogic = new Thread(ServiceLogic);
                threadServiceLogic.Start();
            }
            catch (ArgumentNullException objectArgumentNullException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, "Error starting service logic thread:" + objectArgumentNullException.Message,
                    EventLogEntryType.Error);
            }
            catch (ThreadStateException objectThreadStateException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, "Error starting service logic thread:" + objectThreadStateException.Message,
                    EventLogEntryType.Error);
            }
            catch (OutOfMemoryException objectOutofMemoryException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, "Error starting service logic thread:" + objectOutofMemoryException.Message,
                             EventLogEntryType.Error);
            }
        }

        protected override void OnStop()
        {
            base.OnStop();

            while (threadServiceLogic.ThreadState == System.Threading.ThreadState.Running)
            {
                // waiting infinitely for consumer thread to finish the processing.
            }

            // terminate the consumer thread
            try
            {
                threadServiceLogic.Abort();
                EventLog.WriteEntry(sSource, "Synchronization Service logic thread terminated", EventLogEntryType.Information);
            }
            catch (System.Security.SecurityException objectSecurityException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, "Error aborting service logic thread:" + objectSecurityException.Message,
                    EventLogEntryType.Error);
            }
            catch (ThreadStateException objectThreadStateException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, "Error aborting service logic thread:" + objectThreadStateException.Message,
                    EventLogEntryType.Error);
            }
        }

        public void ServiceLogic()
        {
            YouTubeFunctions objectYouTubeFunctions = new YouTubeFunctions(developerKey, accountName, username, password);
            bool exceptionOccured = false;
            
            // execute this code if and only if we need to synchronize the branch
            while (isSynchronizeBranch)
            {

                if (exceptionOccured)
                {
                    exceptionOccured = false;
                    Thread.Sleep(60000);
                }


                // this function would return the data corresponding to all branches except the specified branch
                List<Tuple<String, Boolean, String, String, String>> videoDataOfOtherBranches =
                    objectYouTubeFunctions.getVideoDataofOtherBranches(username, branchId);



                if (null == videoDataOfOtherBranches)
                {
                    // Exception must have occured while fetching the data from the YouTube
                    // log this event to the Event Logger
                    exceptionOccured = true;
                    EventLog.WriteEntry(sSource, "Error reading data from the YouTube Channel", EventLogEntryType.Error);
                    // we have nothing to process hence use continue
                    continue;
                }

                int count = 0;

                // This videoIdList would be used to remove the dangling files from the audio DB
                // dangling files are those which are downloaded from Dropbox but are now deleted 
                // by the branches which uploaded them

                List<String> videoIdList = null;
                if (videoDataOfOtherBranches.Count >= 0)
                {
                    videoIdList = new List<string>();
                }

                while (count < videoDataOfOtherBranches.Count)
                {
                    EventLog.WriteEntry(sSource, "Processing record of other branches", EventLogEntryType.Information);

                    Tuple<String, Boolean, String, String, String> tuple = videoDataOfOtherBranches[count];
                    String videoId = tuple.Item1;
                    Boolean videoIsPrivateOnYouTube = tuple.Item2;
                    String audioIdOnCloudStorage = tuple.Item3;
                    String branchName = tuple.Item4;
                    String phonenNumber = tuple.Item5;

                    // add videoId to list of videoId
                    videoIdList.Add(videoId);

                    // code to execute if the video is private on the YouTube
                    if (videoIsPrivateOnYouTube)
                    {
                        // check if the audio corresponding to the video is present in the audio DB

                        int statusCode = objectDatabaseFunctions.getModerationStatusOfAudioCorrespondingToYouTubeVideoId(videoId);

                        // if the audio is there in the audio Database and if its public then make it private
                        if (statusCode == 0)
                        {
                            objectDatabaseFunctions.updateModerationStatusForYouTubeVideoId(videoId, true);
                        }

                        // if the corresponding audio file is not present in the DB
                        // or if its already private or if there is a DB error in reading, we do not need to do anything

                    }
                    // code to execute if the video is public on the YouTube
                    else
                    {
                        // check if the audio corresponding to the video is present in the audio DB
                        //DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();

                        int statusCode = objectDatabaseFunctions.getModerationStatusOfAudioCorrespondingToYouTubeVideoId(videoId);

                        // if the audio is there in the audio Database and if its private then make it public
                        if (statusCode == 1)
                        {
                            objectDatabaseFunctions.updateModerationStatusForYouTubeVideoId(videoId, false);
                        }

                        // if the audio is there in the audio Database and if its public or if there is DB read error 
                        //then dont need to do anything

                        // if the corresponding audio file is not present in the DB
                        if (statusCode == 2)
                        {
                            String folderLocation = "C:\\inetpub\\wwwroot\\Uploads\\Downloads\\";

                            bool isDownloadSuccessful = objectFileFunctions.copyFile(dropBoxFolderLocation + audioIdOnCloudStorage
                                , folderLocation);

                            if (isDownloadSuccessful)
                            {
                                EventLog.WriteEntry(sSource, "Audio file downloaded from cloud", EventLogEntryType.Information);

                                //update the audio db
                                String recordingURL = "http://localhost/uploads/downloads/" + audioIdOnCloudStorage;
                                int returnCode = objectDatabaseFunctions.saveRecordedData(folderLocation + audioIdOnCloudStorage,
                                    recordingURL, phonenNumber, branchName, 2, true, false, videoId, 2, 0);
                            }

                        }

                    } // else ends ( the code to be executed if the video is public on YouTube

                    count++;
                } // while loop which checks if more files are needed to be checked on the YouTube 

                // this section of code is just for debugging purpose
                if (videoIdList.Count != videoDataOfOtherBranches.Count)
                {
                    EventLog.WriteEntry(sSource, "Counts are different. This would never happen", EventLogEntryType.Error);
                }
                // this is also for debugging purpose. Over the time, this value should always increase (provided you are not deleting any data)
                // The reason Dangling file would be removed in Branch B would be only if the corresponding file is removed on YT
                EventLog.WriteEntry(sSource, "Video Data of other branch:" + videoDataOfOtherBranches.Count, EventLogEntryType.Information);



                /* here is the code to remove dangling files
                 * This code check whether the files downloaded from cloudStorage are valid or not
                 * If Branch B has downloaded audio corresponding to Branch A then it may be possible that 
                 * though the video is deleted on YouTube and is deleted from Audio DB of Branch A, its still
                 * there in the audio DB of Branch B. To remove this inconsistency, we check each audio file downloaded
                 * from cloudstorage whether it is still there on YouTube, if not then we delete it from the audioDB
                */

                List<Tuple<String, String>> audioDataDownloadedFromCloudStorage
                    = objectDatabaseFunctions.youTubeVideoIdAndAudioFileLocationDownloadedFromCloudStorage();
                if (audioDataDownloadedFromCloudStorage != null)
                {
                    int flag = 0;
                    while (flag < audioDataDownloadedFromCloudStorage.Count)
                    {
                        Tuple<String, String> tuple = audioDataDownloadedFromCloudStorage[flag];

                        if (!videoIdList.Contains(tuple.Item1))
                        {
                            bool isDeletionSuccess = objectDatabaseFunctions.deleteAudioFileFromDatabase(tuple.Item2);
                            // if deletion is success, delete the files from the hard disk
                            if (isDeletionSuccess)
                            {
                                objectFileFunctions.deleteFile(tuple.Item2);
                                EventLog.WriteEntry(sSource, "Dangling file removed: " + tuple.Item1 + " and audio id:" + tuple.Item2,
                                    EventLogEntryType.Information);
                            }
                        }

                        flag++;


                    }

                }


                EventLog.WriteEntry(sSource, "Service logic thread going to sleep", EventLogEntryType.Information);
                //Thread.Sleep(3600000);
                Thread.Sleep(60000);
            } // infinite while loop ends

        }

        public bool internetConnectionAvailable()
        {
            System.Uri Url = new System.Uri("http://www.google.com");

            // by default returning true
            bool internetConnectionAvailableFlag = true;

            System.Net.WebRequest objectWebRequest = null;
            System.Net.WebResponse objectWebResponse;

            try
            {
                objectWebRequest = System.Net.WebRequest.Create(Url);
            }
            catch (System.NotSupportedException notSupportedException)
            {
                // log exceptions
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + notSupportedException.Message, EventLogEntryType.Error);
            }
            catch (System.ArgumentNullException argumentNullException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + argumentNullException.Message, EventLogEntryType.Error);
            }
            catch (System.Security.SecurityException securityException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + securityException.Message, EventLogEntryType.Error);
            }

            try
            {
                objectWebResponse = objectWebRequest.GetResponse();
                objectWebResponse.Close();
                internetConnectionAvailableFlag = true;
                EventLog.WriteEntry(sSource, "Internet connection is available", EventLogEntryType.Information);
            }

            catch (System.NotImplementedException notImplementedException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + notImplementedException.Message, EventLogEntryType.Error);
                internetConnectionAvailableFlag = false;
            }
            catch (Exception generalException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + generalException.Message, EventLogEntryType.Error);
                internetConnectionAvailableFlag = false;
            }
            finally
            {
                objectWebRequest = null;
            }

            return internetConnectionAvailableFlag;
        }
    }
}

